package com.gitee.wsl.doc.prosemirror.state.command

import com.gitee.wsl.doc.prosemirror.model.node.Node
import com.gitee.wsl.doc.prosemirror.model.node.ext.canJoin
import com.gitee.wsl.doc.prosemirror.state.Command
import com.gitee.wsl.doc.prosemirror.state.CommandDispatch
import com.gitee.wsl.doc.prosemirror.transform.ext.join
import com.gitee.wsl.func.plus

/// Wrap a command so that, when it produces a transform that causes
/// two joinable nodes to end up next to each other, those are joined.
/// Nodes are considered joinable when they are of the same type and
/// when the `isJoinable` predicate returns true for them or, if an
/// array of strings was passed, if their node type name is in that
/// array.
fun autoJoin(
    command: Command,
    isJoinable: (before: Node, after: Node) -> Boolean
    ): Command {
    return Command { state, dispatch ->
             command.exec(state, dispatch + wrapDispatchForJoin(dispatch!!, isJoinable))
    }
}

fun Command.autoJoinNode(isJoinable: (before: Node, after: Node) -> Boolean) = autoJoin(this,isJoinable)

fun wrapDispatchForJoin(dispatch: CommandDispatch, isJoinable: (a: Node, b: Node) -> Boolean):CommandDispatch {
    return  CommandDispatch{ tr ->
        // 非通用事务直接分发
        if (!tr.isGeneric) {
            dispatch(tr)
            return@CommandDispatch
        }

        // 收集所有受影响的文本范围
        val ranges = mutableListOf<Int>()
        for (i in tr.mapping.maps.indices) {
            val map = tr.mapping.maps[i]

            // 映射现有范围
            for (j in ranges.indices step 2) {
                ranges[j] = map.map(ranges[j])
                ranges[j + 1] = map.map(ranges[j + 1])
            }

            // 添加新范围
            map.forEach { from, to, _ , _ ->
                ranges.add(from)
                ranges.add(to)
            }
        }

        // 识别可合并的位置
        val joinable = mutableListOf<Int>()
        for (i in ranges.indices step 2) {
            val fromPos = ranges[i]
            val toPos = ranges[i + 1]
            val from = tr.doc.resolve(fromPos)
            val depth = from.sharedDepth(toPos)
            val parent = from.node(depth)

            var index = from.indexAfter(depth)
            var pos = from.after(depth + 1)

            while (pos <= toPos) {
                val after = parent.maybeChild(index) ?: break

                // 检查前一个节点是否可合并
                if (index > 0 && !joinable.contains(pos)) {
                    val before = parent.child(index - 1)
                    if (before.type == after.type && isJoinable(before, after)) {
                        joinable.add(pos)
                    }
                }

                pos += after.nodeSize
                index++
            }
        }

        // 按位置排序并执行合并（从后往前避免位置偏移）
        joinable.sort()
        for (i in joinable.indices.reversed()) {
            val joinPos = joinable[i]
            if (tr.doc.canJoin( joinPos)) {
                tr.join(joinPos)
            }
        }

        dispatch(tr)
    }
}

/**
 * function wrapDispatchForJoin(dispatch: (tr: Transaction) => void, isJoinable: (a: Node, b: Node) => boolean) {
 *   return (tr: Transaction) => {
 *     if (!tr.isGeneric) return dispatch(tr)
 *
 *     let ranges: number[] = []
 *     for (let i = 0; i < tr.mapping.maps.length; i++) {
 *       let map = tr.mapping.maps[i]
 *       for (let j = 0; j < ranges.length; j++)
 *         ranges[j] = map.map(ranges[j])
 *       map.forEach((_s, _e, from, to) => ranges.push(from, to))
 *     }
 *
 *     // Figure out which joinable points exist inside those ranges,
 *     // by checking all node boundaries in their parent nodes.
 *     let joinable = []
 *     for (let i = 0; i < ranges.length; i += 2) {
 *       let from = ranges[i], to = ranges[i + 1]
 *       let $from = tr.doc.resolve(from), depth = $from.sharedDepth(to), parent = $from.node(depth)
 *       for (let index = $from.indexAfter(depth), pos = $from.after(depth + 1); pos <= to; ++index) {
 *         let after = parent.maybeChild(index)
 *         if (!after) break
 *         if (index && joinable.indexOf(pos) == -1) {
 *           let before = parent.child(index - 1)
 *           if (before.type == after.type && isJoinable(before, after))
 *             joinable.push(pos)
 *         }
 *         pos += after.nodeSize
 *       }
 *     }
 *     // Join the joinable points
 *     joinable.sort((a, b) => a - b)
 *     for (let i = joinable.length - 1; i >= 0; i--) {
 *       if (canJoin(tr.doc, joinable[i])) tr.join(joinable[i])
 *     }
 *     dispatch(tr)
 *   }
 * }
 */